package fun.mashuai.generator.util;

import com.google.common.base.CaseFormat;
import freemarker.template.TemplateExceptionHandler;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.*;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author 马帅
 * @version 1.0
 * @className CodeUtil.java
 * @description TODO
 * @date 2020/4/14 17:51
 */
public class CodeUtil {

    /**
     * JDBC配置，请修改为你项目的实际配置
     */
    private static final String JDBC_URL = "jdbc:mysql://114.247.222.106:10006/walle?characterEncoding=utf-8";
    private static final String JDBC_USERNAME = "root";
    private static final String JDBC_PASSWORD = "123456";
    private static final String JDBC_DIVER_CLASS_NAME = "com.mysql.jdbc.Driver";

    /**
     * 生成代码所在的基础包名称，可根据自己公司的项目修改
     * 注意：这个配置修改之后需要手工修改src目录项目默认的包路径，使其保持一致，不然会找不到类
     */
    private static final String BASE_PACKAGE = "fun.mashuai.generator";
    /**
     * 生成的entity所在包
     */
    private static final String MODEL_PACKAGE = BASE_PACKAGE + ".pojo";
    /**
     * 生成的mapper所在包
     */
    private static final String MAPPER_PACKAGE = BASE_PACKAGE + ".mapper";
    /**
     * 生成的service所在包
     */
    private static final String SERVICE_PACKAGE = BASE_PACKAGE + ".service";
    /**
     * 生成的serviceImpl所在包
     */
    private static final String SERVICE_IMPL_PACKAGE = SERVICE_PACKAGE + ".impl";
    /**
     * 生成的controller所在包
     */
    private static final String CONTROLLER_PACKAGE = BASE_PACKAGE + ".controller";
    /**
     * Mapper插件基础接口的完全限定名
     */
    //private static final String MAPPER_INTERFACE_REFERENCE = BASE_PACKAGE + ".core.Mapper";


    /**
     * 项目在硬盘上的基础路径
     */
    private static final String PROJECT_PATH = PathUtil.getProjectPath();
    /**
     * 模板位置
     */
    private static final String TEMPLATE_FILE_PATH = PROJECT_PATH + "/src/test/resources/generator/template";
    /**
     * java文件路径
     */
    private static final String JAVA_PATH = "/src/main/java";
    /**
     * 资源文件路径
     */
    private static final String RESOURCES_PATH = "/src/main/resources";

    /**
     * 生成的Service存放路径
     */
    private static final String PACKAGE_PATH_SERVICE = packageConvertPath(SERVICE_PACKAGE);
    /**
     * 生成的Service实现存放路径
     */
    private static final String PACKAGE_PATH_SERVICE_IMPL = packageConvertPath(SERVICE_IMPL_PACKAGE);
    /**
     * 生成的Controller存放路径
     */
    private static final String PACKAGE_PATH_CONTROLLER = packageConvertPath(CONTROLLER_PACKAGE);

    /**
     * 作者
     */
    private static final String AUTHOR = "马帅";
    /**
     * 日期
     */
    private static final String DATE = new SimpleDateFormat("yyyy/MM/dd HH:mm").format(new Date());


    public static void main(String[] args) {
        genCode("sys_email");
        //genCodeByCustomModelName("输入表名","输入自定义Model名称");
    }



    /**
     * 通过数据表名称生成代码，Model 名称通过解析数据表名称获得，下划线转大驼峰的形式。
     * 如输入表名称 "t_user_detail" 将生成 TUserDetail、TUserDetailMapper、TUserDetailService ...
     * @param tableNames 数据表名称...
     */
    public static void genCode(String... tableNames) {
        for (String tableName : tableNames) {
            genCodeByCustomModelName(tableName, null);
        }
    }

    /**
     * 通过数据表名称，和自定义的 Model 名称生成代码
     * 如输入表名称 "t_user_detail" 和自定义的 Model 名称 "User" 将生成 User、UserMapper、UserService ...
     * @param tableName 数据表名称
     * @param modelName 自定义的 Model 名称
     */
    public static void genCodeByCustomModelName(String tableName, String modelName) {
        //genModelAndMapper(tableName, modelName);
        genEntityAndMapper(tableName, modelName, false);
        //genService(tableName, modelName);
        //genController(tableName, modelName);
    }


    public static void genModelAndMapper(String tableName, String modelName) {


        List<String> warnings = new ArrayList<>();

        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config;
        MyBatisGenerator generator;
        try {
            config = cp.parseConfiguration(CodeUtil.class.getClassLoader().getResourceAsStream("generatorConfig.xml"));
            Context context = config.getContext("Mysql");

            JDBCConnectionConfiguration jdbcConnectionConfiguration = context.getJdbcConnectionConfiguration();
            jdbcConnectionConfiguration.setConnectionURL(JDBC_URL);
            jdbcConnectionConfiguration.setUserId(JDBC_USERNAME);
            jdbcConnectionConfiguration.setPassword(JDBC_PASSWORD);
            jdbcConnectionConfiguration.setDriverClass(JDBC_DIVER_CLASS_NAME);
            context.setJdbcConnectionConfiguration(jdbcConnectionConfiguration);

            // 生成model模型，对应的包路径
            JavaModelGeneratorConfiguration javaModelGeneratorConfiguration = context.getJavaModelGeneratorConfiguration();
            javaModelGeneratorConfiguration.setTargetProject(PROJECT_PATH + JAVA_PATH);
            javaModelGeneratorConfiguration.setTargetPackage(MODEL_PACKAGE);
            context.setJavaModelGeneratorConfiguration(javaModelGeneratorConfiguration);

            // mapper 映射文件
            SqlMapGeneratorConfiguration sqlMapGeneratorConfiguration = context.getSqlMapGeneratorConfiguration();
            sqlMapGeneratorConfiguration.setTargetProject(PROJECT_PATH + RESOURCES_PATH);
            sqlMapGeneratorConfiguration.setTargetPackage("mapper");
            context.setSqlMapGeneratorConfiguration(sqlMapGeneratorConfiguration);

            // mapper 接口
            JavaClientGeneratorConfiguration javaClientGeneratorConfiguration = context.getJavaClientGeneratorConfiguration();
            javaClientGeneratorConfiguration.setTargetProject(PROJECT_PATH + JAVA_PATH);
            javaClientGeneratorConfiguration.setTargetPackage(MAPPER_PACKAGE);
            javaClientGeneratorConfiguration.setConfigurationType("XMLMAPPER");
            context.setJavaClientGeneratorConfiguration(javaClientGeneratorConfiguration);

            // 重新配置 table 参数
            List<TableConfiguration> tableConfigurations = context.getTableConfigurations();
            tableConfigurations.clear();
            TableConfiguration tableConfiguration = new TableConfiguration(context);
            tableConfiguration.setTableName(tableName);
            tableConfiguration.setCountByExampleStatementEnabled(false);
            tableConfiguration.setDeleteByExampleStatementEnabled(false);
            tableConfiguration.setUpdateByExampleStatementEnabled(false);
            tableConfiguration.setSelectByExampleStatementEnabled(false);
            tableConfiguration.setSelectByPrimaryKeyStatementEnabled(true);

            tableConfigurations.add(tableConfiguration);

            if (StringUtils.isNotEmpty(modelName)) {
                tableConfiguration.setDomainObjectName(modelName);
            }

            DefaultShellCallback callback = new DefaultShellCallback(true);

            generator = new MyBatisGenerator(config, callback, warnings);
            generator.generate(null);

        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("生成Model和Mapper失败", e);
        }


        if (generator.getGeneratedJavaFiles().isEmpty() || generator.getGeneratedXmlFiles().isEmpty()) {
            throw new RuntimeException("生成Model和Mapper失败：" + warnings);
        }
        if (StringUtils.isEmpty(modelName)) {
            modelName = tableNameConvertUpperCamel(tableName);
        }
        System.out.println(modelName + ".java 生成成功");
        System.out.println(modelName + "Mapper.java 生成成功");
        System.out.println(modelName + "Mapper.xml 生成成功");
    }


    public static void genEntityAndMapper(String tableName, String modelName, Boolean isUseExample) {

        Context context = new Context(ModelType.FLAT);
        context.setId("Mysql");
        context.setTargetRuntime("MyBatis3");
        /* beginningDelimiter: 起始分隔符
         * endingDelimiter: 结束分隔符
         * 由于beginningDelimiter和endingDelimiter的默认值为双引号(")，
         * 在Mysql中不能这么写，所以还要将这两个默认值改为**反单引号(`)**
         */
        context.addProperty(PropertyRegistry.CONTEXT_BEGINNING_DELIMITER, "`");
        context.addProperty(PropertyRegistry.CONTEXT_ENDING_DELIMITER, "`");

        context.addProperty("autoDelimitKeywords", "true");
        context.addProperty("javaFileEncoding", "UTF-8");


        // 插件相关
        PluginConfiguration plugin1 = new PluginConfiguration();
        PluginConfiguration plugin2 = new PluginConfiguration();
        PluginConfiguration plugin3 = new PluginConfiguration();
        PluginConfiguration plugin4 = new PluginConfiguration();
        PluginConfiguration plugin5 = new PluginConfiguration();

        // 集成lombok插件
        plugin1.addProperty("type", "fun.mashuai.generator.plugins.LombokPlugin");
        plugin1.setConfigurationType("fun.mashuai.generator.plugins.LombokPlugin");
        context.addPluginConfiguration(plugin1);

        // 序列化
        plugin2.addProperty("type", "org.mybatis.generator.plugins.SerializablePlugin");
        plugin2.setConfigurationType("org.mybatis.generator.plugins.SerializablePlugin");
        context.addPluginConfiguration(plugin2);

        plugin3.addProperty("type", "fun.mashuai.generator.plugins.CustomMapperPlugin");
        plugin3.setConfigurationType("fun.mashuai.generator.plugins.CustomMapperPlugin");
        context.addPluginConfiguration(plugin3);

        plugin4.addProperty("type", "fun.mashuai.generator.plugins.BaseMapperInterfacePlugin");
        if(isUseExample){
            plugin4.addProperty("hasExample", "true");
        }
        plugin4.setConfigurationType("fun.mashuai.generator.plugins.BaseMapperInterfacePlugin");
        context.addPluginConfiguration(plugin4);

        plugin5.addProperty("type", "fun.mashuai.generator.plugins.EntityPlugin");
        plugin5.setConfigurationType("fun.mashuai.generator.plugins.EntityPlugin");
        context.addPluginConfiguration(plugin5);



        // 去除自动生成的注释信息，去除自动生成的时间戳，避免重复提交SVN
        CommentGeneratorConfiguration commentGeneratorConfiguration = new CommentGeneratorConfiguration();
        commentGeneratorConfiguration.addProperty("suppressAllComments", "true");
        commentGeneratorConfiguration.addProperty("suppressDate", "true");
        context.setCommentGeneratorConfiguration(commentGeneratorConfiguration);

        JDBCConnectionConfiguration jdbcConnectionConfiguration = new JDBCConnectionConfiguration();
        jdbcConnectionConfiguration.setConnectionURL(JDBC_URL);
        jdbcConnectionConfiguration.setUserId(JDBC_USERNAME);
        jdbcConnectionConfiguration.setPassword(JDBC_PASSWORD);
        jdbcConnectionConfiguration.setDriverClass(JDBC_DIVER_CLASS_NAME);
        // 避免扫描其他数据库表
        jdbcConnectionConfiguration.addProperty("nullCatalogMeansCurrent", "true");
        context.setJdbcConnectionConfiguration(jdbcConnectionConfiguration);

        // 生成model模型，对应的包路径
        JavaModelGeneratorConfiguration javaModelGeneratorConfiguration = new JavaModelGeneratorConfiguration();
        javaModelGeneratorConfiguration.setTargetProject(PROJECT_PATH + JAVA_PATH);
        javaModelGeneratorConfiguration.setTargetPackage(MODEL_PACKAGE);
        javaModelGeneratorConfiguration.addProperty("rootClass", "fun.mashuai.generator.pojo.BaseEntity");
        context.setJavaModelGeneratorConfiguration(javaModelGeneratorConfiguration);

        // mapper 映射文件
        SqlMapGeneratorConfiguration sqlMapGeneratorConfiguration = new SqlMapGeneratorConfiguration();
        sqlMapGeneratorConfiguration.setTargetProject(PROJECT_PATH + RESOURCES_PATH);
        sqlMapGeneratorConfiguration.setTargetPackage("mapper");
        context.setSqlMapGeneratorConfiguration(sqlMapGeneratorConfiguration);

        // mapper 接口
        JavaClientGeneratorConfiguration javaClientGeneratorConfiguration = new JavaClientGeneratorConfiguration();
        javaClientGeneratorConfiguration.setTargetProject(PROJECT_PATH + JAVA_PATH);
        javaClientGeneratorConfiguration.setTargetPackage(MAPPER_PACKAGE);
        javaClientGeneratorConfiguration.setConfigurationType("XMLMAPPER");
        context.setJavaClientGeneratorConfiguration(javaClientGeneratorConfiguration);

        TableConfiguration tableConfiguration = new TableConfiguration(context);
        tableConfiguration.setTableName(tableName);
        tableConfiguration.setCountByExampleStatementEnabled(isUseExample);
        tableConfiguration.setDeleteByExampleStatementEnabled(isUseExample);
        tableConfiguration.setUpdateByExampleStatementEnabled(isUseExample);
        tableConfiguration.setSelectByExampleStatementEnabled(isUseExample);
        tableConfiguration.setSelectByPrimaryKeyStatementEnabled(true);

        if (StringUtils.isNotEmpty(modelName)) {
            tableConfiguration.setDomainObjectName(modelName);
        }
        context.addTableConfiguration(tableConfiguration);


        List<String> warnings;
        MyBatisGenerator generator;
        try {
            Configuration config = new Configuration();
            config.addContext(context);
            config.validate();

            warnings = new ArrayList<>();

            DefaultShellCallback callback = new DefaultShellCallback(true);

            generator = new MyBatisGenerator(config, callback, warnings);
            generator.generate(null);

        } catch (Exception e) {
            throw new RuntimeException("生成Model和Mapper失败", e);
        }



        if (generator.getGeneratedJavaFiles().isEmpty() || generator.getGeneratedXmlFiles().isEmpty()) {
            throw new RuntimeException("生成Model和Mapper失败：" + warnings);
        }
        if (StringUtils.isEmpty(modelName)) {
            modelName = tableNameConvertUpperCamel(tableName);
        }
        System.out.println(modelName + ".java 生成成功");
        System.out.println(modelName + "Mapper.java 生成成功");
        System.out.println(modelName + "Mapper.xml 生成成功");
    }






    public static void genService(String tableName, String modelName) {
        try {
            freemarker.template.Configuration cfg = getConfiguration();

            Map<String, Object> data = new HashMap<>();
            data.put("date", DATE);
            data.put("author", AUTHOR);
            String modelNameUpperCamel = StringUtils.isEmpty(modelName) ? tableNameConvertUpperCamel(tableName) : modelName;
            data.put("modelNameUpperCamel", modelNameUpperCamel);
            data.put("modelNameLowerCamel", tableNameConvertLowerCamel(tableName));
            data.put("basePackage", BASE_PACKAGE);

            File file = new File(PROJECT_PATH + JAVA_PATH + PACKAGE_PATH_SERVICE + modelNameUpperCamel + "Service.java");
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            cfg.getTemplate("service.ftl").process(data,
                    new FileWriter(file));
            System.out.println(modelNameUpperCamel + "Service.java 生成成功");

            File file1 = new File(PROJECT_PATH + JAVA_PATH + PACKAGE_PATH_SERVICE_IMPL + modelNameUpperCamel + "ServiceImpl.java");
            if (!file1.getParentFile().exists()) {
                file1.getParentFile().mkdirs();
            }
            cfg.getTemplate("service-impl.ftl").process(data,
                    new FileWriter(file1));
            System.out.println(modelNameUpperCamel + "ServiceImpl.java 生成成功");
        } catch (Exception e) {
            throw new RuntimeException("生成Service失败", e);
        }
    }

    public static void genController(String tableName, String modelName) {
        try {
            freemarker.template.Configuration cfg = getConfiguration();

            Map<String, Object> data = new HashMap<>();
            data.put("date", DATE);
            data.put("author", AUTHOR);
            String modelNameUpperCamel = StringUtils.isEmpty(modelName) ? tableNameConvertUpperCamel(tableName) : modelName;
            data.put("baseRequestMapping", modelNameConvertMappingPath(modelNameUpperCamel));
            data.put("modelNameUpperCamel", modelNameUpperCamel);
            data.put("modelNameLowerCamel", CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, modelNameUpperCamel));
            data.put("basePackage", BASE_PACKAGE);

            File file = new File(PROJECT_PATH + JAVA_PATH + PACKAGE_PATH_CONTROLLER + modelNameUpperCamel + "Controller.java");
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            //cfg.getTemplate("controller-restful.ftl").process(data, new FileWriter(file));
            cfg.getTemplate("controller-restful.ftl").process(data, new FileWriter(file));

            System.out.println(modelNameUpperCamel + "Controller.java 生成成功");
        } catch (Exception e) {
            throw new RuntimeException("生成Controller失败", e);
        }

    }

    private static freemarker.template.Configuration getConfiguration() throws IOException {
        freemarker.template.Configuration cfg = new freemarker.template.Configuration(freemarker.template.Configuration.VERSION_2_3_23);
        cfg.setDirectoryForTemplateLoading(new File(TEMPLATE_FILE_PATH));
        cfg.setDefaultEncoding("UTF-8");
        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
        return cfg;
    }

    private static String tableNameConvertLowerCamel(String tableName) {
        return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, tableName.toLowerCase());
    }

    private static String tableNameConvertUpperCamel(String tableName) {
        return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, tableName.toLowerCase());

    }

    private static String tableNameConvertMappingPath(String tableName) {
        // 兼容使用大写的表名
        tableName = tableName.toLowerCase();
        return "/" + (tableName.contains("_") ? tableName.replaceAll("_", "/") : tableName);
    }

    private static String modelNameConvertMappingPath(String modelName) {
        String tableName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, modelName);
        return tableNameConvertMappingPath(tableName);
    }

    private static String packageConvertPath(String packageName) {
        return String.format("/%s/", packageName.contains(".") ? packageName.replaceAll("\\.", "/") : packageName);
    }

}
